/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/21 22:31
  *
  * @file       pd_i2c_hd.c
  * @brief      I2C hardware peripheral driver.
  *
  * @note       This file contains the hardware driver for the I2C.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/21     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_gpio.h>
#include <stm32f4xx_ll_bus.h>
#include <stm32f4xx_ll_i2c.h>
#include <delay.h>
#include "pd_i2c_hd.h"


static void I2CHardware_init_(const I2C* super) {
    const I2CHardware* this = (I2CHardware *)super;

    GPIO_init(this->i2c.sck);
    GPIO_init(this->i2c.sda);

    if      (this->I2Cx == I2C1) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C1);
    else if (this->I2Cx == I2C2) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C2);
    else if (this->I2Cx == I2C3) LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_I2C3);

    LL_I2C_DisableOwnAddress2(this->I2Cx);
    LL_I2C_DisableGeneralCall(this->I2Cx);
    LL_I2C_EnableClockStretching(this->I2Cx);

    LL_I2C_InitTypeDef I2C_InitStruct;
    LL_I2C_StructInit(&I2C_InitStruct);
    I2C_InitStruct.PeripheralMode = this->PeripheralMode;
    I2C_InitStruct.ClockSpeed = this->ClockSpeed;
    I2C_InitStruct.DutyCycle = this->DutyCycle;
#if  defined(I2C_FLTR_ANOFF) && defined(I2C_FLTR_DNF)
    I2C_InitStruct.AnalogFilter    = LL_I2C_ANALOGFILTER_ENABLE;
    I2C_InitStruct.DigitalFilter   = 0U;
#endif
    I2C_InitStruct.OwnAddress1 = this->OwnAddress1;
    I2C_InitStruct.TypeAcknowledge = this->TypeAcknowledge;
    I2C_InitStruct.OwnAddrSize = this->OwnAddrSize;
    LL_I2C_Init(this->I2Cx, &I2C_InitStruct);
    LL_I2C_SetOwnAddress2(this->I2Cx, 0);
}

static uint8_t I2CHardware_send_(const I2C *super, const uint8_t salveAddress, const uint16_t regAddress,
                                 uint8_t *dat, const uint8_t len) {
    const I2CHardware* this = (I2CHardware *)super;

    if (len == 0) return 1;

    while (LL_I2C_IsActiveFlag_BUSY(this->I2Cx));

    // I2C start and send slave address
    LL_I2C_GenerateStartCondition(this->I2Cx);
    // Wait for EV5: BUSY, MSL, SB
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_SB(this->I2Cx));


    // Reset the address bit0 for write
    LL_I2C_TransmitData8(this->I2Cx, (salveAddress << 1) | 0);
    // Wait for EV6: BUSY, MSL, ADDR, TXE and TRA
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_ADDR(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_TXE(this->I2Cx));
    while (!LL_I2C_GetTransferDirection(this->I2Cx));


    // I2C send register address that wait to write
    if ((regAddress & 0xFF00) == 0) {
        LL_I2C_TransmitData8(this->I2Cx, regAddress);
    } else {
        LL_I2C_TransmitData8(this->I2Cx, regAddress >> 8);
        delay_us(1);
        LL_I2C_TransmitData8(this->I2Cx, regAddress);
    }
    // Wait for EV8: BUSY, MSL, TXE, TRA
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_TXE(this->I2Cx));
    while (!LL_I2C_GetTransferDirection(this->I2Cx));


    // I2C send data
    for (int i = 0; i < len; ++i) {
        LL_I2C_TransmitData8(this->I2Cx, dat[i]);
        // Wait for EV8_2: TRA, BUSY, MSL, TXE and BTF
        while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
        while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
        while (!LL_I2C_IsActiveFlag_TXE(this->I2Cx));
        while (!LL_I2C_GetTransferDirection(this->I2Cx));
        while (!LL_I2C_IsActiveFlag_BTF(this->I2Cx));
    }


    // I2C stop
    LL_I2C_GenerateStopCondition(this->I2Cx);
    //    while (!LL_I2C_IsActiveFlag_STOP(this->I2Cx)); // error if exists

    return 0;
}


static uint8_t I2CHardware_read_(const I2C *super, const uint8_t salveAddress, const uint16_t regAddress,
                                 uint8_t *dat, const uint8_t len) {
    const I2CHardware* this = (I2CHardware *)super;

    while (LL_I2C_IsActiveFlag_BUSY(this->I2Cx));


    // I2C start and send slave address
    LL_I2C_GenerateStartCondition(this->I2Cx);
    // Wait for EV5: BUSY, MSL, SB
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_SB(this->I2Cx));

    // I2C send slave address and Write
    LL_I2C_TransmitData8(this->I2Cx, (salveAddress << 1) | 0);
    // Wait for EV6: BUSY, MSL, ADDR, TXE and TRA
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_ADDR(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_TXE(this->I2Cx));
    while (!LL_I2C_GetTransferDirection(this->I2Cx));


    // I2C send register address that wait to read
    if ((regAddress & 0xFF00) == 0) {
        LL_I2C_TransmitData8(this->I2Cx, regAddress);
    } else {
        LL_I2C_TransmitData8(this->I2Cx, regAddress >> 8);
        delay_us(1);
        LL_I2C_TransmitData8(this->I2Cx, regAddress);
    }
    // Wait for EV8_2: BUSY, MSL, TXE, TRA, BTF
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_TXE(this->I2Cx));
    while (!LL_I2C_GetTransferDirection(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_BTF(this->I2Cx));


    // I2C start and send slave address
    LL_I2C_GenerateStartCondition(this->I2Cx);
    // Wait for EV5: BUSY, MSL, SB
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_SB(this->I2Cx));


    // I2C send slave address and read
    LL_I2C_TransmitData8(this->I2Cx, (salveAddress << 1) | 1);
    // Wait for EV6: BUSY, MSL, ADDR
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_ADDR(this->I2Cx));


    for (int i = 0; i < len - 1; ++i) {
        // I2C send ack(1)
        LL_I2C_AcknowledgeNextData(this->I2Cx, LL_I2C_ACK);
        // Wait for EV7: BUSY, MSL, RXNE
        while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
        while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
        while (!LL_I2C_IsActiveFlag_RXNE(this->I2Cx));

        dat[i] = LL_I2C_ReceiveData8(this->I2Cx);
    }

    // I2C send ack(1)
    LL_I2C_AcknowledgeNextData(this->I2Cx, LL_I2C_ACK);

    // Wait for EV7: BUSY, MSL, RXNE
    while (!LL_I2C_IsActiveFlag_BUSY(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_MSL(this->I2Cx));
    while (!LL_I2C_IsActiveFlag_RXNE(this->I2Cx));

    LL_I2C_AcknowledgeNextData(this->I2Cx, LL_I2C_NACK);

    // I2C receive last byte
    dat[len - 1] = LL_I2C_ReceiveData8(this->I2Cx);

    // I2C stop
    LL_I2C_GenerateStopCondition(this->I2Cx);
    //    while (!LL_I2C_IsActiveFlag_STOP(this->I2Cx)); // error if exists

    return 0;
}

void I2CHardware_constructor(I2CHardware* this, I2C_TypeDef* I2Cx, GPIO* sck, GPIO* sda) {
    static struct I2CVtbl vtbl = {
        .init = &I2CHardware_init_,
        .read = &I2CHardware_read_,
        .send = &I2CHardware_send_,
    };
    sck->Speed      = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    sck->Mode       = LL_GPIO_MODE_ALTERNATE;
    sck->OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
    sck->Pull       = LL_GPIO_PULL_NO;
    sck->Alternate  = LL_GPIO_AF_4;

    sda->Speed      = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    sda->Mode       = LL_GPIO_MODE_ALTERNATE;
    sda->OutputType = LL_GPIO_OUTPUT_OPENDRAIN;
    sda->Pull       = LL_GPIO_PULL_NO;
    sda->Alternate  = LL_GPIO_AF_4;
    I2C_constructor(&this->i2c, sck, sda);

    this->i2c.vptr = &vtbl;

    this->I2Cx = I2Cx;
    this->PeripheralMode  = LL_I2C_MODE_I2C;
    this->ClockSpeed      = 100000U;
    this->DutyCycle       = LL_I2C_DUTYCYCLE_2;
#if  defined(I2C_FLTR_ANOFF) && defined(I2C_FLTR_DNF)
    this->AnalogFilter    = LL_I2C_ANALOGFILTER_ENABLE;
    this->DigitalFilter   = 0U;
#endif
    this->OwnAddress1     = 0U;
    this->TypeAcknowledge = LL_I2C_ACK;
    this->OwnAddrSize     = LL_I2C_OWNADDRESS1_7BIT;
}
